package com.lx.constant.others.options.impl.redis;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.lx.constant.others.options.CacheOption;
import com.lx.inter.query.WriteCacheCallBack;
import lombok.Getter;
import lombok.ToString;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;

import java.util.concurrent.locks.Lock;

/**
 * Redis缓存参数项
 * 场景解释:
 * 1. [redisClient字段]定义为[Object类型],原因为无法确定调用方传入的
 *
 * @author lx
 * @create 2023/1/31 14:10
 **/
@Getter
@ToString(callSuper = true)
public class RedisCacheOption<T extends RedisClient> extends CacheOption {

    public RedisCacheOption(T redisClient, String cacheName) {
        this(redisClient, null, cacheName);
    }

    /**
     * Redis缓存参数项
     * 提供具备读写分离的缓存存取写入性
     *
     * @param redisClient
     * @param redissonClient
     * @param cacheName
     */
    public RedisCacheOption(T redisClient, RedissonClient redissonClient, String cacheName) {
        super(cacheName);
        this.redisClient = redisClient;
        this.redissonClient = redissonClient;
    }

    /**
     * Redisson分布式锁客户端(可不传,则不会维护分布式下的读写有序性)
     */
    private final RedissonClient redissonClient;

    /**
     * Redis客户端执行器
     */
    private final T redisClient;

    /**
     * 是否开启压缩,默认false;如预测结果为占用较大的数据,建议开启
     * TODO 暂未实现,根据后续应用场景多少来决定
     */
    private boolean compact = false;

    /**
     * 提供默认缓存机制
     *
     * @param redisClient Redis客户端执行器
     * @param cacheName   缓存名称
     * @return
     */
    public static RedisCacheOption defaultOptions(RedisClient redisClient, String cacheName) {
        RedisCacheOption cacheOption = new RedisCacheOption(redisClient, null, cacheName);
        return cacheOption;
    }

    /**
     * 提供默认缓存机制(支持分布式有序获取缓存)
     *
     * @param redisClient    Redis客户端执行器
     * @param redissonClient RedissonClient,用于保证缓存读写时的有序性
     * @param cacheName      缓存名称
     * @return
     */
    public static RedisCacheOption defaultOptions(RedisClient redisClient, RedissonClient redissonClient, String cacheName) {
        RedisCacheOption cacheOption = new RedisCacheOption(redisClient, redissonClient, cacheName);
        return cacheOption;
    }

    /**
     * 调用分布式读写锁获取缓存数据,如缓存结果不存在,则在获取写锁的情况下进行缓存数据写入
     *
     * @param writeCacheCallBack
     * @param <T>
     * @return
     */
    @Override
    public <T> T handlerByCache(WriteCacheCallBack writeCacheCallBack) {
        if (this.getCacheName() == null) {
            //记录日志且不计入缓存,结果数据调用查询.
            log.debug("Redis缓存统一调用中,不存在缓存名称,因此不进行数据缓存!");
            return (T) writeCacheCallBack.getTargetData();
        }
        return (T) (redissonClient == null ? getResultObjByNonLock(writeCacheCallBack) : getResultObjByReadWriteLock(writeCacheCallBack));
    }

    /**
     * 校验和初始化使用缓存的必须参数
     */
    @Override
    protected void checkAndInitOptions() {
        super.checkAndInitOptions();

        if (getRedissonClient() == null) {
            //add by lx Date:2023/2/28 Desc: 允许不传入分布式锁控对象
            //throw new NullPointerException("未设置Redis分布式锁对象,无法进行缓存查询相关操作!");
        }

        if (getRedisClient() == null) {
            throw new NullPointerException("未设置Redis缓存对象,无法进行缓存查询相关操作!");
        }
    }

    /**
     * 无读写锁传入的处理方案
     *
     * @param writeCacheCallBack
     * @return
     */
    private Object getResultObjByNonLock(WriteCacheCallBack writeCacheCallBack) {
        final String cacheName = getCacheName();
        final T redisClient = this.getRedisClient();

        String cacheString = redisClient.get(cacheName);
        if (cacheString != null) {
            return JSONObject.parseObject(cacheString, this.getCacheConvertClass());
        }
        //获取需要缓存的数据
        try {
            return writeCacheCallBack.getTargetData();
        } catch (Throwable e) {
            log.error("Redis缓存统一调用中,获取写入数据时业务方调用出现异常,本次缓存调用入参为=[" + this + "],异常原因:", e);
            throw e;
        }
    }

    /**
     * 有读写锁处理方案
     * 支持单一且有序的分布式缓存存储处理方案
     *
     * @param writeCacheCallBack
     * @return
     */
    private Object getResultObjByReadWriteLock(WriteCacheCallBack writeCacheCallBack) {
        final String cacheName = getCacheName();
        //注入读写锁
        final RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(LOCK_PREFIX + cacheName);
        final T redisClient = this.getRedisClient();

        //执行读逻辑,判断是否能够获取数据.
        RLock readLock = readWriteLock.readLock();
        readLock.lock();
        try {
            String cacheString = redisClient.get(cacheName);
            if (cacheString != null) {
                return JSONObject.parseObject(cacheString, this.getCacheConvertClass());
            }
        } finally {
            readLock.unlockAsync();
        }

        //未设置缓存或缓存失效,执行写缓存逻辑.
        Lock writeLock = readWriteLock.writeLock();
        writeLock.lock();
        try {
            //写锁成功被获取场景下,可以执行无锁逻辑操作
            Object res = getResultObjByNonLock(writeCacheCallBack);
            //设置缓存
            try {
                //目标数据不存在,防止穿透,设置空串,在JSON解析结果时会返回null. todo 后续考虑进行缓存初始值
                String cacheResString = res == null ? StrUtil.EMPTY : JSON.toJSONString(res);
                //获取过期时间
                int expireSeconds = Math.toIntExact(this.getTimeUnit().toSeconds(this.getTimeOut()));
                //写入Redis
                if (expireSeconds <= 0) {
                    redisClient.set(cacheName, cacheResString);
                } else {
                    redisClient.setEx(cacheName, expireSeconds, cacheResString);
                }
            } catch (Exception e) {
                log.error("Redis缓存统一调用中,写入结果到Redis缓存出现异常,本次缓存调用入参为=[" + this + "],结果数据=[" + res + "],异常原因:", e);
            }
            return res;
        } finally {
            writeLock.unlock();
        }
    }
}
